/**
 * Copyright (c) 2008 Trent Hoeppner
 * 
 * This file is part of BillCalc.
 * 
 * BillCalc is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * BillCalc is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with BillCalc.  If not, see <http://www.gnu.org/licenses/>.
 **/

package com.trentech.billcalc.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.io.FileNotFoundException;

import javax.swing.AbstractListModel;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.trentech.billcalc.FinanceManager;
import com.trentech.billcalc.event.Event;
import com.trentech.billcalc.event.EventFactory;

/**
 * EventView is allows the user to view, edit and create individual events.
 * 
 * @author Trent Hoeppner
 */
public class EventView extends JPanel {

    /**
     * Required for serialization.
     */
    private static final long serialVersionUID = 4935973324073141637L;
    
    
    /**
     * The FinanceManager to get Events from.
     */
    private FinanceManager manager;
    
    
    /**
     * The component for displaying and editing Event data.
     */
    private EventEditor eventEditor;
    
    
    /**
     * The component that the user can use to select Events.
     */
    private JList eventList;
    
    
    /**
     * The Action which is used to remove events.
     */
    private RemoveEventAction removeEventAction;
    
    
    /**
     * The Action which is used to save events.
     */
    private SaveAllAction saveAllAction;
    
    
    /**
     * The manager for tooltips and label highlighting.
     */
    private UserFriendlinessManager ufManager;


    /**
     * Constructor for EventView.
     * 
     * @param   manager     the FinanceManager that is used to validate Event 
     *                      data.  Cannot be null.
     * @param   ufManager   the manager for tooltips and label highlighting.  
     *                      Cannot be null.
     */
    public EventView(FinanceManager manager, UserFriendlinessManager ufManager) {
        this.manager = manager;
        this.ufManager = ufManager;
        
        setLayout(new BorderLayout());
        
        JPanel listAndButtonPanel = new JPanel();
        listAndButtonPanel.setLayout(new BorderLayout());
        eventList = new JList();
        eventList.addListSelectionListener(new EventSelectionListener());
        listAndButtonPanel.add(new JScrollPane(eventList), BorderLayout.CENTER);
        
        eventList.setCellRenderer(new EventRenderer());
        eventList.setModel(new EventListModel());
        
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
        JMenuBar menuBar = new JMenuBar();
        JMenu newEventButton = new JMenu();
        
        newEventButton.setIcon(BillCalcFrame.loadImage("plus.gif"));
        newEventButton.setMinimumSize(newEventButton.getPreferredSize());
        ufManager.registerToolTip(newEventButton, 
                Messages.getString("EventView.addButton.description"));
        menuBar.add(newEventButton);
        
        addNewEventAction(newEventButton, EventFactory.SetupBillEvent);
        addNewEventAction(newEventButton, EventFactory.ArriveEvent);
        addNewEventAction(newEventButton, EventFactory.StartLeaseEvent);
        addNewEventAction(newEventButton, EventFactory.RenewLeaseEvent);
        addNewEventAction(newEventButton, EventFactory.TransferLeaseEvent);
        addNewEventAction(newEventButton, EventFactory.LeaveEvent);
        addNewEventAction(newEventButton, EventFactory.BillEvent);
        addNewEventAction(newEventButton, EventFactory.PayEvent);
        addNewEventAction(newEventButton, EventFactory.OptOutEvent);
        addNewEventAction(newEventButton, EventFactory.OptInEvent);
        addNewEventAction(newEventButton, EventFactory.StartVacationEvent);
        addNewEventAction(newEventButton, EventFactory.StopVacationEvent);
        
        buttonPanel.add(menuBar);
        removeEventAction = new RemoveEventAction();
        JButton removeEventButton = new JButton(removeEventAction);
        
        ufManager.registerToolTip(removeEventButton, 
                Messages.getString("EventView.removeEventButton.description"));
        buttonPanel.add(removeEventButton);
        saveAllAction = new SaveAllAction();
        JButton saveAllButton = new JButton(saveAllAction);
        
        ufManager.registerToolTip(saveAllButton, 
                Messages.getString("EventView.saveAllButton.description"));
        buttonPanel.add(saveAllButton);
        listAndButtonPanel.add(buttonPanel, BorderLayout.SOUTH);
        
        eventEditor = new EventEditor(manager, ufManager);
        eventEditor.setListener(new EventEditListener());
        
        JSplitPane bigSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 
                true, listAndButtonPanel, new JScrollPane(eventEditor));
        add(bigSplit, BorderLayout.CENTER);
        
        adjustSaveButton();
        adjustRemoveButton();
    }
    
    
    /**
     * Tells the manager to reprocess Events, and if necessary, reorganize the
     * Events that can be selected.
     * 
     * @param   requiresSorting true indicates that the order or number of 
     *                          events has changed, false indicates that 
     *                          only simple event data has changed.
     */
    private void updateEvents(boolean requiresSorting) {
        if (requiresSorting) {
            manager.resortEvents();
        }
        manager.processEvents();
        eventList.setModel(new EventListModel());
        eventList.validate();
        eventList.repaint();
        
        adjustSaveButton();
    }
    
    
    /**
     * Adds an Action to the given menu for the Event associated with the given 
     * factory.
     * 
     * @param   newEventButton  the menu that acts as the "new event" button.  
     *                          Cannot be null.
     * @param   factory         the EventFactory that is used to update 
     */
    private void addNewEventAction(JMenu newEventButton, EventFactory factory) {
    	JMenuItem menuItem = new JMenuItem(new NewEventAction(factory));
        ufManager.registerEventComponent(factory, menuItem);
        newEventButton.add(menuItem);
    }
    
    
    /**
     * Makes the save button enabled or disabled depending on whether save is 
     * necessary.
     */
    private void adjustSaveButton() {
        boolean enabled = !manager.isEventsSaved();
        saveAllAction.setEnabled(enabled);
    }
    
    
    /**
     * Makes the remove event button enabled or disabled depending on if there 
     * are events selected, and whether those events can be removed.  Events 
     * which are automatically generated cannot be removed.
     */
    private void adjustRemoveButton() {
        boolean enabled = !eventList.isSelectionEmpty();
        for (Object item : eventList.getSelectedValues()) {
            EventWrapper wrapper = (EventWrapper) item;
            if (wrapper.getEvent().isGenerated()) {
                enabled = false;
                break;
            }
        }
        
        removeEventAction.setEnabled(enabled);
    }
    
    
    /**
     * EventListModel is a model to be used in the JList for events.  It wraps
     * each event in an EventWrapper object so that the text can be customized 
     * (using toString()) in the JList.
     */
    private class EventListModel extends AbstractListModel {

        /**
         * Required for serialization.
         */
        private static final long serialVersionUID = -1788469326288755344L;
        
        
        /**
         * Constructor for EventListModel.
         */
        public EventListModel() {
            // do nothing
        }


        /**
         * {@inheritDoc}
         */
        public Object getElementAt(int index) {
            EventWrapper wrapper = new EventWrapper(manager.getEvents().get(index));
            return wrapper;
        }


        /**
         * {@inheritDoc}
         */
        public int getSize() {
            return manager.getEvents().size();
        }
    }
    
    
    /**
     * EventWrapper wraps an Event so that the toString() method can customize
     * text output.  This is to keep the Event classes "pure" and free of GUI-
     * related issues.
     */
    private class EventWrapper {
        
        /**
         * The Event that is wrapped.
         */
        private Event event;


        /**
         * Constructor for EventWrapper.
         * 
         * @param   event   the Event to wrap.  Cannot be null.
         */
        public EventWrapper(Event event) {
            this.event = event;
        }
        
        
        /**
         * Returns the wrapped Event.
         * 
         * @return  the wrapped Event.  Will not be null.
         */
        public Event getEvent() {
            return event;
        }
        
        
        /**
         * {@inheritDoc}
         */
        public String toString() {
            String key = event.getClass().getSimpleName() + ".listMessage";
            String displayName = Messages.getCompoundString(key, event.getDate(), event.getName());
            return displayName;
        }
    }
    

    /**
     * EventEditListener listens to save and cancel events from the EventEditor.
     */
    private class EventEditListener implements EventEditorListener {
        
        /**
         * Constructor for EventEditListener.
         */
        public EventEditListener() {
            // do nothing
        }
        

        /**
         * {@inheritDoc}
         */
        public void editingCancelled(Event event) {
            eventEditor.setEvent(null, false);
        }
        

        /**
         * {@inheritDoc}
         */
        public void saved(Event event) {
            // if the data for the event changed, the order might change, so 
            // must resort
            manager.resortEvents();
            
            manager.addEvent(event);
            updateEvents(true);
        }
        
    }
    
    
    /**
     * EventRenderer highlights Events that are not saved, or are generated, 
     * in a different color.
     */
    private class EventRenderer extends DefaultListCellRenderer {
        
        /**
         * Required for serialization.
         */
        private static final long serialVersionUID = 1519109850561875305L;

        
        /**
         * Constructor for EventRenderer.
         */
        public EventRenderer() {
            // do nothing
        }
        

        /**
         * {@inheritDoc}
         */
        public Component getListCellRendererComponent(JList list, Object value,
                int index, boolean isSelected, boolean cellHasFocus) {
            Component component = super.getListCellRendererComponent(list, value, index, isSelected,
                    cellHasFocus);
            
            Event event = ((EventWrapper) value).getEvent();
            if (event.isGenerated()) {
                component.setForeground(Color.LIGHT_GRAY.darker());
            }
            else if (!event.isSaved()) {
                component.setForeground(Color.BLUE);
            }
            
            return component;
        }
        
    }
    
    
    /**
     * EventSelectionListener listens for changes to the selection of 
     * Events, and updates the view accordingly.
     */
    private class EventSelectionListener implements ListSelectionListener {
        
        /**
         * Constructor for EventSelectionListener.
         */
        public EventSelectionListener() {
            // do nothing
        }
        

        /**
         * {@inheritDoc}
         */
        public void valueChanged(ListSelectionEvent e) {
            if (e.getValueIsAdjusting()) {
                return;
            }
            
            Event event;
            JList list = (JList) e.getSource();
            if (list.getSelectedIndex() < 0) {
                event = null;
                eventEditor.setEvent(event, false);
            }
            else {
                Object[] items = list.getSelectedValues();
                event = ((EventWrapper) items[0]).getEvent();
                eventEditor.setEvent(event, true);
            }
            
            // ensure that the latest event is still selected
            if (event != null) {
                if (list.getSelectedValue() != event) {
                    list.setSelectedValue(event, true);
                }
            }
            else {
                list.setSelectedValue(event, false);
            }
            
            adjustRemoveButton();
        }
    }
    /**
     * RemoveEventAction removes an Event from the FinanceManager.
     */
    private class RemoveEventAction extends SimpleAction {
        
        /**
         * Required for serialization.
         */
        private static final long serialVersionUID = -8467553151635016298L;


        /**
         * Constructor for RemoveEventAction.
         */
        public RemoveEventAction() {
            super(null, "minus.gif");
        }
        
        
        /**
         * Asks to confirm deletion, and if so, removes the selected Events.
         * 
         * @param   e   the event that triggered this call.  Ignored.
         */
        public void actionPerformed(ActionEvent e) {
            Object[] eventsToRemove = eventList.getSelectedValues();
            if (eventsToRemove.length <= 0) {
                return;
            }
            
            int result = JOptionPane.showConfirmDialog(EventView.this, 
                    Messages.getString("EventView.deleteConfirm"),
                    Messages.getString("EventView.deleteConfirmTitle"),
                    JOptionPane.YES_NO_OPTION);
            if (result == JOptionPane.YES_OPTION) {
                for (Object event : eventsToRemove) {
                    manager.removeEvent(((EventWrapper) event).getEvent());
                }
                updateEvents(true);
                eventEditor.setEvent(null, false);
            }
        }
    }
    
    
    /**
     * NewEventAction is the base class for Actions that create new Events.
     */
    private class NewEventAction extends SimpleAction {
        
        /**
         * Required for serialization.
         */
        private static final long serialVersionUID = -865216880892815718L;
        
        
        /**
         * The EventFactory that can be used to generate empty Events.
         */
        private EventFactory factory;


        /**
         * Constructor for NewEventAction.
         * 
         * @param   factory the EventFactory that can be used to generate empty
         *                  Events for creation.  Cannot be null.
         */
        public NewEventAction(EventFactory factory) {
            super(factory.getLocalizedName(), null);
            this.factory = factory;
        }


        /**
         * Creates the Event and puts it in the editor so the user can change 
         * the values.
         * 
         * @param   e   the event that triggered this call.  Ignored.
         */
        public void actionPerformed(ActionEvent e) {
            Event event = factory.createNewEvent();
            eventEditor.setEvent(event, false);
        }
    }
    
    
    /**
     * SaveAllAction saves all Events to the events file.
     */
    private class SaveAllAction extends SimpleAction {

        /**
         * Required for serialization.
         */
        private static final long serialVersionUID = -5985623991833945342L;

        
        /**
         * Constructor for SaveAllAction.
         */
        public SaveAllAction() {
            super(null, "download_green.gif");
        }


        /**
         * Creates the Event and puts it in the editor so the user can change 
         * the values.
         * 
         * @param   e   the event that triggered this call.  Ignored.
         */
        public void actionPerformed(ActionEvent e) {
            try {
                manager.saveEvents();
                eventList.repaint();
                adjustSaveButton();
            }
            catch (FileNotFoundException e1) {
                e1.printStackTrace();
            }
        }
        
    }
}
